CloudFrontのレスポンスヘッダーポリシーでCORSに対応する
こんにちは、なおにしです。
CloudFront+S3の構成でCORS設定を試してみたのでご紹介します。
はじめに
「CORS」(読み方:コルスが一般的のようなので私もそれに倣っています)の仕組みについては解説記事がWeb上に大量にあるのでそちらをご参照いただければと思いますが、MDN Web Docsでは以下のように説明されています。
CORS (オリジン間リソース共有、 Cross-Origin Resource Sharing) は、 HTTP ヘッダーの転送で構成されるシステムであり、ブラウザーがオリジンをまたいだリクエストのレスポンスに、フロントエンドの JavaScript コードがアクセスすることをブロックするかどうかを決めるものです。
言葉どおりではあるのですが、いまいちイメージがつきにくいことから解説記事が増えているのだと思います(本記事もその一つです)。
このため、今回はブラウザからのアクセスで実際に挙動を確認しながら、CloudFrontのレスポンスヘッダーポリシーを使用してCORS設定を行ってみます。なお、レスポンスヘッダーポリシー自体は提供開始から数年経過しているもので、詳細は以下の記事をご参照ください。
やってみた
前提
以下のようにCloudFrontのオリジンとして、EC2インスタンスをバックエンドにしたALBと、静的コンテンツを格納したS3バケットをそれぞれ指定する構成で考えてみます。
EC2インスタンスには以下のテスト用HTMLファイルをドキュメントルートに格納しました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>CloudFront CORS テスト</title>
</head>
<body>
<h1>CloudFront と S3 を使用した CORS テストページ</h1>
<p>このページでは、ALB から提供される HTML と S3 から提供される画像が正しく表示されることを確認します。</p>
<div class="image-container">
<h2>imgタグで表示</h2>
<img src="https://www.example.com/images/sample-image.png" alt="サンプル画像 1">
</div>
<div class="image-container">
<h2>canvasタグとjsで表示</h2>
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #000000;"></canvas>
</div>
<script>
// 画像をキャンバスに描画して CORS をテスト
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = "https://www.example.com/images/sample-image.png";
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 画像データの取得 (CORS 設定が正しくないとエラーになります)
try {
const imageData = canvas.toDataURL();
console.log("画像データ取得に成功");
} catch (error) {
console.error("画像データ取得に失敗:", error);
}
};
img.onerror = function(err) {
console.error("画像の読み込みエラー:", err);
};
</script>
</body>
</html>
CloudFrontのビヘイビアでは以下のように/images/配下のファイルを指定したときはオリジンとしてS3を、それ以外の時はALBに振り分けられるように設定しています。
パスパターン「/images/*」のビヘイビアでは、以下のようにキャッシュ無効でオリジンリクエストポリシーとレスポンスヘッダーポリシーも未設定の状態です。後ほどCORSに対応するために設定を変更していきます。
また、オリジンのS3は静的Webサイトホスティングは無効で、OACを用いた接続になっています。
CloudFrontでCORS関連の設定をしていない場合
まず、CloudFront経由(構成図のwww.example.com)でアクセスした場合は以下のとおりです。
画像が2枚表示されています。S3に格納されている画像ファイル自体は1つですが、上部の画像はimgタグで表示したもので、下部の画像はjavascriptを用いて描写したものになります。CloudFrontではキャッシュを無効に設定しているので、Chrome DevToolsで表示されているとおり今回はS3から画像を取得することができています。
続いてALB経由(構成図のalb.example.com)でアクセスした場合は以下のとおりです。
下部の画像だけ表示されませんでした。Statusにも「CORS error」と表示されています。javascript内のエラーハンドリングによって出力された内容は以下のとおりです。
Access to image at 'https://alb.example.com/images/sample-image.png' from origin 'https://alb.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORSのポリシーによって画像ファイルへのアクセスがブロックされた旨が出力されていることを確認できました。
それではCloudFrontの設定を変更することで、上記のCORSによってブロックされた画像が表示できるように修正していきます。
CloudFront ビヘイビアの修正
まず、オリジンリクエストポリシーを設定します。オリジンリクエストポリシーではマネージドポリシーとして「CORS-S3Origin」が準備されています。詳細はドキュメントをご確認ください。「CORS-S3Origin」ポリシーでは、以下3つのヘッダーをオリジンリクエストに含めます。各ヘッダーの解説はMDN Web Docsから引用しました。
-
Origin
- どのオリジンからアクセスしているかを示します。
-
Access-Control-Request-Headers
- プリフライトリクエストを発行する際に、実際のリクエストを行う際に使用される HTTP ヘッダーをサーバーに知らせるために使用します。
-
Access-Control-Request-Method
- プリフライトリクエストを発行する際に、実際のリクエストを行う際に使用される HTTP メソッドをサーバーに知らせるために使用します。
つまり、「CORS-S3Origin」ポリシーではプリフライトリクエストに使用されるヘッダーが対象となっています。具体的にどのようなリクエストが行われるのかについてはMDN Web Docsをご参照ください。プリフライトリクエスト自体は以下のように解説されています。
CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。
これは OPTIONS リクエストであり、 Access-Control-Request-Method,Access-Control-Request-Headers, Origin の 3 つの HTTP リクエストヘッダー使用します。
プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが "to be preflighted" と修飾されている場合に現れ、単純リクエストの場合は省略されます。
続いて、レスポンスヘッダーポリシーを設定します。レスポンスヘッダーポリシーについてはAWSマネジメントコンソールでポリシーを作成しようとすると「クロスオリジンリソース共有 (CORS) -オプション」として各CORSヘッダーの設定を行うことができます。
オリジンリクエストポリシーと同様にレスポンスヘッダーポリシーでもマネージドポリシーとして「CORS-With-Preflight」が準備されています。詳細はドキュメントをご確認ください。「CORS-With-Preflight」ポリシーはマネジメントコンソール上では以下のように設定されていることが確認できます。
ドキュメントに記載のある「オーバーライドオリジンですか。:いいえ」についてはマネジメントコンソール画面上で表示されていない=いいえという意味のようなので、念のためCLIで確認したところ、確かに「"OriginOverride": false」となっていました。
$ aws cloudfront get-response-headers-policy --id 5cc3b908-e619-4b99-88e5-2cf7f45965bd
{
"ETag": "E23ZP02F085DFQ",
"ResponseHeadersPolicy": {
"Id": "5cc3b908-e619-4b99-88e5-2cf7f45965bd",
"LastModifiedTime": "1970-01-01T00:00:00+00:00",
"ResponseHeadersPolicyConfig": {
"Comment": "Allows all origins for CORS requests, including preflight requests",
"Name": "Managed-CORS-With-Preflight",
"CorsConfig": {
"AccessControlAllowOrigins": {
"Quantity": 1,
"Items": [
"*"
]
},
"AccessControlAllowHeaders": {
"Quantity": 0,
"Items": []
},
"AccessControlAllowMethods": {
"Quantity": 7,
"Items": [
"GET",
"HEAD",
"PUT",
"POST",
"PATCH",
"DELETE",
"OPTIONS"
]
},
"AccessControlAllowCredentials": false,
"AccessControlExposeHeaders": {
"Quantity": 1,
"Items": [
"*"
]
},
"OriginOverride": false
}
}
}
}
したがって、MDN Web Docsに記載がある9つのCORSヘッダーが、オリジンリクエストポリシーとレスポンスヘッダーポリシーによって全て設定できることが確認できました。一旦はレスポンスヘッダーポリシーとして「CORS-With-Preflight」ポリシーを設定して、ビヘイビアの設定としては以下のとおりで再度ブラウザアクセスを確認してみます。
ALB経由のアクセスでも表示できるようになりました。URLをぼかしているためスクリーンショットが本当にALB経由のアクセスなのかどうか違いが分かりづらいかと思いますが、とりあえずCORSのレスポンスヘッダーが存在することは確認できます。
オリジンを制限する場合
「CORS-With-Preflight」ポリシーでは「Access-Control-Allow-Origin:*」となっているため、今回の構成でいうと「alb.example.com」以外からもCORSによって制限されずにアクセスできてしまいます。「alb.example.com」からのアクセスのみ応答できるようにするには、レスポンスヘッダーポリシーでカスタムポリシーを作成する必要があります。「CORS-With-Preflight」ポリシーを参考に、例えば以下のように作成できます。
以下のように「Access-Control-Allow-Origin」で指定したオリジンが返ってくるようになります。なお、上記の「alb.example.com」を「hoge.example.com」のような別ドメインに設定すると、もちろん下部の画像はCORSエラーになって表示されませんでした。
S3バケットのCORS設定について
ここまでの確認では、CORS設定がレスポンスヘッダーポリシーで完結しているため、以下のとおりS3バケット自体のCORS設定は何も行っていません。
今回の検証ではS3バケットで静的ウェブサイトホスティングを有効にしていませんが、上記の設定が機能するのか確認してみます。
S3バケットへのCORS設定については以下のドキュメントに記載されています。
上記のドキュメントを参考に、S3バケットにCORS設定を行いました。
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"HEAD"
],
"AllowedOrigins": [
"https://alb.example.com"
],
"ExposeHeaders": []
}
]
併せて、CloudFront のビヘイビアではレスポンスヘッダーポリシーを無効化します。
ALB経由のアクセスを再度ブラウザから確認したところ、以下のように表示できました。ちなみにS3バケットのCORS設定を削除すると下部の画像のみ表示できなくなりました。
「Access-Control-Allow-Credentials」ヘッダーについてはS3バケットのCORS設定では制御できなさそうでしたが、実際にはtrueとなっている挙動はドキュメントに記載の内容と一致しています。
A Boolean that determines if the server allows CORS requests to contain credentials. If the
Access-Control-Allow-Origin
request header is set to'*'
then theAccess-Control-Allow-Credentials
response header will be omitted, else it is set totrue
when CORS evaluation is successful.
以上より、CORS設定はCloudFrontのレスポンスヘッダーポリシーとS3バケットのどちらで設定しても良いことが分かりました。逆にいうと、明確な意図が無い限りは両方を設定する必要はないかと思います。両方を設定していた場合、レスポンスヘッダーポリシー内のオリジンのオーバーライド有無でも挙動が異なるため、イメージしづらくなる可能性もあります。
まとめ
CORS に関する色々な記事を読んでいる際、CloudFront+S3構成の場合はS3バケットでCORSを設定していたりしていなかったり、curlによるアクセスで挙動を確認しているため私の理解力ではいまいちイメージが湧きにくかったりしたため、実際にブラウザを使用してCORSの挙動を確認してみました。
本記事がどなたかのお役に立てれば幸いです。